package net.dwade.dubbo.adapter;

import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.spring.ReferenceBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * Dubbo消息费者适配器，用于适配dubbo服务的provider，如果是原生的dubbo接口、服务适配的接口都配置了，在开发模式下，
 * 会移除服务适配的接口，在非开发模式下会移除原生的dubbo
 * <p>
 * eg: &lt;dubbo:reference id="paymentService" interface="net.dwade.dubbo.adapter.PaymentServiceStringAdapter" /&gt;
 * </p>
 * @author huangxf
 * @date 2017年7月23日
 */
public class DubboConsumerAdapterSupport implements BeanDefinitionRegistryPostProcessor, InitializingBean {
	
	private Logger logger = LoggerFactory.getLogger( this.getClass() );
	
	/**
	 * 用于保存接口适配的bean定义
	 */
	private Set<BeanDefinition> stringAdapterDefinition = new HashSet<BeanDefinition>();
	
	/**
	 * 用于保存已创建的适配器
	 */
	private Set<Object> createdAdapter = new HashSet<Object>();
	
	/**
	 * 默认使用jdk的动态代理
	 */
	private ServiceAdapterProxyFactory proxyFactory;

	public void setProxyFactory(ServiceAdapterProxyFactory proxyFactory) {
		Assert.notNull( proxyFactory, "proxyFactory is null." );
		this.proxyFactory = proxyFactory;
	}

	/**
	 * 动态生成适配的接口，并且创建动态代理类代理对原生接口的调用
	 */
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		for ( BeanDefinition definition : stringAdapterDefinition ) {
			String interfaceName = (String)definition.getPropertyValues().get( "interface" );
			try {
				Object adapter = this.createAndRegisterAdapter( definition, beanFactory );
				createdAdapter.add( adapter );
				logger.info( "create adapter instance:{}, interface:{}", adapter, interfaceName );
			} catch ( Throwable e ) {
				logger.error( "error create adapter for interface:{}", interfaceName );
				throw new BeanCreationException( "DubboConsumerAdapterSupport create adapter error", e );
			}
		}
	}

	/**
	 * 创建XXXService的动态代理实现类，并且注册到spring容器中
	 * @param referenceBeanDef
	 * @param beanFactory
	 * @return 返回XXXService的动态代理实现类
	 * @throws ClassNotFoundException
	 * @throws LinkageError
	 * @throws IOException
	 */
	protected Object createAndRegisterAdapter( BeanDefinition referenceBeanDef, ConfigurableListableBeanFactory beanFactory ) 
			throws Throwable {
		
		// String适配接口类名，eg:net.dwade.dubbo.adapter.PaymentServiceStringAdapter
		final String targetInterfaceName = (String)referenceBeanDef.getPropertyValues().get( "interface" );
		final String sourceInterfaceName = StringUtils.removeEnd( targetInterfaceName, "StringAdapter" );
		Class<?> sourceInterfaceClass = ClassUtils.forName( sourceInterfaceName, null );
		
		// 根据原接口动态生成String入参出参的适配接口，否则dubbo找不到对应的接口定义
		Class<?> adapterInterfaceClass = StringAdapterInterfaceUtils.
				createAndWriteByteCode(sourceInterfaceName, targetInterfaceName);
		
		// dubbo根据<dubbo:reference />生成的动态代理类
		String dubboBeanName = (String)referenceBeanDef.getPropertyValues().get( "id" );
		Object dubboProxyBean = beanFactory.getBean( dubboBeanName );
		
		// 动态代理实现类，因为暴露的是XXXServiceStringAdapter接口的dubbo服务，而我们的代码层面是使用XXXService进行调用
		Object proxy = proxyFactory.createConsumerProxy( dubboProxyBean, sourceInterfaceClass, adapterInterfaceClass );

		// 将XXXService的动态代理的实现类注入到spring容器中
		String sourceBeanName = StringUtils.uncapitalize( sourceInterfaceClass.getSimpleName() ) + "$Adapter";
		beanFactory.registerSingleton( sourceBeanName, proxy );
		return proxy;
		
	}
	
	/**
	 * 判断是否是满足条件的接口
	 * @param refrenceBeanDef
	 * @return
	 */
	protected boolean isCandidateInterface( BeanDefinition refrenceBeanDef ) {
		String interfaceName = (String)refrenceBeanDef.getPropertyValues().get( "interface" );
		if ( interfaceName.endsWith( "StringAdapter" ) ) {
			return true;
		}
		return false;
	}

	/**
	 * 遍历所有的bean定义，如果是ReferenceBean并且满足一定的条件，则添加到{@link #stringAdapterDefinition}中，便于后续创建动态代码
	 * <p>
	 * 如果是原生的dubbo接口，和服务适配的接口都配置了，在开发模式下，会移除服务适配的接口，在非开发模式下会移除原生的dubbo
	 * </p>
	 */
	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
		String[] names = registry.getBeanDefinitionNames();
		
		// 用于保存原有的<dubbo:refrence />定义的bean，便于后续从spring容器中移除，key为接口名称
		Map<String, BeanDefinitionHolder> sourceHolderMap = new HashMap<String, BeanDefinitionHolder>();
		Map<String, BeanDefinitionHolder> targetHolderMap = new HashMap<String, BeanDefinitionHolder>();
		for ( String name : names ) {
			BeanDefinition definition = registry.getBeanDefinition( name );
			String beanClassName = definition.getBeanClassName();
			if ( ReferenceBean.class.getName().equals( beanClassName ) ) {
				String interfaceName = (String)definition.getPropertyValues().get( "interface" );
				BeanDefinitionHolder holder = new BeanDefinitionHolder( name, interfaceName, definition );
				if ( !isCandidateInterface( definition ) ) {
					sourceHolderMap.put( interfaceName, holder );
				} else {
					stringAdapterDefinition.add( definition );
					targetHolderMap.put( interfaceName, holder );
				}
			}
		}

		logger.info( "detected string adapter definitions:{}", stringAdapterDefinition );
		
		for ( Entry<String, BeanDefinitionHolder> entry : targetHolderMap.entrySet() ) {
			
			String adapterInterfaceName = entry.getKey();
			String sourceInterfaceName = StringUtils.removeEnd( adapterInterfaceName, "StringAdapter" );
			BeanDefinitionHolder sourceDef = sourceHolderMap.get( sourceInterfaceName );
			
			// 只存在适配的dubbo，而不存在原生的dubbo定义
			if ( sourceDef == null ) {
				continue;
			} else {
				// 移除原有的bean定义
				registry.removeBeanDefinition( sourceDef.beanName );
			}
		}
		
	}

	/**
	 * 打印相关日志，并清除临时数据
	 */
	@Override
	public void afterPropertiesSet() throws Exception {
		logger.info( "created string adapter proxy:{}", createdAdapter );
		stringAdapterDefinition.clear();
		createdAdapter.clear();
	}
	
	private class BeanDefinitionHolder {
		
		String beanName;
		
		String interfaceName;
		
		BeanDefinition definition;
		
		BeanDefinitionHolder( String beanName, String interfaceName, BeanDefinition definition ) {
			this.beanName = beanName;
			this.interfaceName = interfaceName;
			this.definition = definition;
		}
		
	}

}